package com.anjlab.eclipse.tapestry5.internal.visitors;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TypeLiteral;
import com.anjlab.eclipse.tapestry5.EclipseUtils;
import com.anjlab.eclipse.tapestry5.TapestryModule;
import com.anjlab.eclipse.tapestry5.internal.DeclarationCapturingScope;
import com.anjlab.eclipse.tapestry5.internal.DeclarationCapturingScope.Declaration;
import com.anjlab.eclipse.tapestry5.internal.DeclarationCapturingScope.InjectedDeclaration;
public abstract class TapestryServiceConfigurationCapturingVisitor extends ASTVisitor
{
protected final IProgressMonitor monitor;
protected final TapestryModule tapestryModule;
protected final DeclarationCapturingScope declarations;
private Object name;
private Object value;
private boolean captureConfiguration;
private boolean captureOrderedConfiguration;
private boolean captureMappedConfiguration;
public TapestryServiceConfigurationCapturingVisitor(IProgressMonitor monitor, TapestryModule tapestryModule)
{
this.monitor = monitor;
this.tapestryModule = tapestryModule;
this.declarations = new DeclarationCapturingScope();
}
public TapestryServiceConfigurationCapturingVisitor usesConfiguration()
{
this.captureConfiguration = true;
return this;
}
public TapestryServiceConfigurationCapturingVisitor usesOrderedConfiguration()
{
this.captureOrderedConfiguration = true;
return this;
}
public TapestryServiceConfigurationCapturingVisitor usesMappedConfiguration()
{
this.captureMappedConfiguration = true;
return this;
}
@Override
public boolean visit(MethodDeclaration node)
{
// Capture method arguments in a scope
declarations.enterScope();
for (Object arg : node.parameters())
{
if (arg instanceof SingleVariableDeclaration)
{
SingleVariableDeclaration variableDeclaration = (SingleVariableDeclaration) arg;
String className = EclipseUtils.toClassName(tapestryModule.getEclipseProject(), variableDeclaration.getType());
if (StringUtils.isNotEmpty(className))
{
declarations.add(new Declaration(
variableDeclaration,
variableDeclaration.getName().getIdentifier(),
className));
}
}
}
return super.visit(node);
}
@Override
public void endVisit(MethodDeclaration node)
{
super.endVisit(node);
declarations.exitScope();
}
@Override
public boolean visit(MethodInvocation node)
{
if (monitor.isCanceled())
{
return false;
}
this.name = null;
this.value = null;
// org.apache.tapestry5.ioc.Configuration<T>
// org.apache.tapestry5.ioc.OrderedConfiguration<T>
// org.apache.tapestry5.ioc.MappedConfiguration<K,V>
if (!isTapestryConfigurationMethod(node))
{
return false;
}
if (!(node.getExpression() instanceof SimpleName))
{
return false;
}
Object type = resolve(node.getExpression());
if (type instanceof InjectedDeclaration)
{
type = ((InjectedDeclaration) type).type;
}
if (!(type instanceof IType))
{
return false;
}
String className = ((IType) type).getFullyQualifiedName();
int argc = node.arguments().size();
if (StringUtils.equals("org.apache.tapestry5.ioc.Configuration", className)
&& captureConfiguration)
{
if (argc == 1)
{
this.value = resolve(node.arguments().get(0));
configurationAddOverride(node, this.value);
return false;
}
}
else if (StringUtils.equals("org.apache.tapestry5.ioc.OrderedConfiguration", className)
&& captureOrderedConfiguration)
{
if (argc >= 2)
{
this.name = resolve(node.arguments().get(0));
if (!(this.name instanceof String))
{
// Contribution id must be String
return false;
}
this.value = resolve(node.arguments().get(1));
orderedConfigurationAddOverride(
node,
(String) this.name,
this.value,
resolveConstraints(node, 2));
return false;
}
}
else if (StringUtils.equals("org.apache.tapestry5.ioc.MappedConfiguration", className)
&& captureMappedConfiguration)
{
if (argc == 2)
{
this.name = resolve(node.arguments().get(0));
this.value = resolve(node.arguments().get(1));
mappedConfigurationAddOverride(node, this.name, this.value);
return false;
}
}
return false;
}
private String[] resolveConstraints(MethodInvocation node, int constraintsArgumentIndex)
{
int argc = node.arguments().size();
List<String> constraints = new ArrayList<String>();
if (argc == constraintsArgumentIndex + 1)
{
// XXX May be: String, or String[], or chain of OrderConstraintBuilder
Object arg = node.arguments().get(constraintsArgumentIndex);
if ((arg instanceof String) || (arg instanceof StringLiteral))
{
Object constraint = resolve(arg);
if (constraint instanceof String)
{
constraints.add((String) constraint);
}
}
}
else
{
// Varargs
for (int i = constraintsArgumentIndex; i < argc; i++)
{
Object arg = node.arguments().get(i);
Object constraint = resolve(arg);
if (constraint instanceof String)
{
constraints.add((String) constraint);
}
}
}
return constraints.toArray(new String[constraints.size()]);
}
private Object resolve(Object arg)
{
if (arg instanceof TypeLiteral)
{
String className = EclipseUtils.toClassName(tapestryModule.getEclipseProject(), (TypeLiteral) arg);
if (StringUtils.isNotEmpty(className))
{
return EclipseUtils.findTypeDeclaration(tapestryModule.getEclipseProject(), className);
}
}
else if (arg instanceof SimpleName)
{
String identifier = ((SimpleName) arg).getIdentifier();
Declaration declaration = declarations.findClosest(identifier);
if (declaration != null && StringUtils.isNotEmpty(declaration.className))
{
// Create injected resource, it may be:
// - @InjectService("id"),
// - with zero-or-many @Marker annotations,
// - or @Inject @Symbol(name)
IType type = EclipseUtils.findTypeDeclaration(tapestryModule.getEclipseProject(), declaration.className);
return type == null ? null : new InjectedDeclaration(declaration, type);
// This may be an interface name of @Inject'ed service.
// Interface name is enough right now, but it may be handy
// to find corresponding implementation class to implement additional features.
// Finding an implementation is not a trivial operation:
// - a service may be injected by ID,
// - or using marker annotations,
// and we may not have enough information at this time,
// because not all modules could be discovered yet.
// We should keep the declaration reference to find this information later.
}
}
else if (arg instanceof ClassInstanceCreation)
{
String className = EclipseUtils.toClassName(
tapestryModule.getEclipseProject(), ((ClassInstanceCreation) arg).getType());
if (StringUtils.isNotEmpty(className))
{
return EclipseUtils.findTypeDeclaration(tapestryModule.getEclipseProject(), className);
}
}
// Try evaluating
return EclipseUtils.evalExpression(tapestryModule.getEclipseProject(), arg);
}
private boolean isTapestryConfigurationMethod(MethodInvocation node)
{
return Arrays.binarySearch(TapestryModule.CONFIGURATION_METHODS, node.getName().toString()) >= 0;
}
protected boolean isAddOrOverrideInvocation(MethodInvocation node)
{
return Arrays.binarySearch(TapestryModule.ADD_OVERRIDE, node.getName().toString()) >= 0;
}
protected boolean isAddOrOverrideInstanceInvocation(MethodInvocation node)
{
return Arrays.binarySearch(TapestryModule.ADD_OVERRIDE_INSTANCE, node.getName().toString()) >= 0;
}
protected boolean isOverride(MethodInvocation node)
{
return Arrays.binarySearch(TapestryModule.OVERRIDES, node.getName().toString()) >= 0;
}
protected void mappedConfigurationAddOverride(MethodInvocation node, Object key, Object value)
{
// Do nothing, concrete visitors should override this method
}
protected void orderedConfigurationAddOverride(MethodInvocation node, String id, Object value, String[] constraints)
{
// Do nothing, concrete visitors should override this method
}
protected void configurationAddOverride(MethodInvocation node, Object value)
{
// Do nothing, concrete visitors should override this method
}
}